﻿using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Devonline.Identity;

/// <summary>
/// 认证类扩展方法
/// </summary>
public static class IdentityExtensions
{
    /// <summary>
    /// 将认证相关默认设置放入依赖注入容器
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultIdentity<TIdentityDbContext>(this IServiceCollection services) where TIdentityDbContext : DbContext
    {
        services.AddIdentity<User, Role>(options =>
        {
            // Password settings.
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequireNonAlphanumeric = true;
            options.Password.RequireUppercase = true;
            options.Password.RequiredLength = 8;
            options.Password.RequiredUniqueChars = 1;

            // Lockout settings.
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(1);
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.AllowedForNewUsers = true;

            // User settings.
            options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
            options.User.RequireUniqueEmail = false;

            // Identity Store.
            options.Stores.MaxLengthForKeys = 36;
            //options.Stores.ProtectPersonalData = true;
        })
            .AddEntityFrameworkStores<TIdentityDbContext>()
            .AddErrorDescriber<DefaultIdentityErrorDescriber>()
            .AddPersonalDataProtection<AesProtector, DefaultKeyRing>()
            .AddDefaultTokenProviders();

        services.TryAddScoped<DbContext, TIdentityDbContext>();
        services.TryAddScoped<UserStore>();
        services.TryAddScoped<RoleStore>();
        services.TryAddScoped<GroupStore>();
        services.TryAddScoped<ResourceAccessStore>();
        services.TryAddScoped<DefaultIdentityErrorDescriber>();
        return services;
    }

    /// <summary>
    /// 针对字符串类型主键的 IIdentity 实例获取对应的 IdentityType
    /// </summary>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType(this IIdentity<string> identity) => identity.GetIdentityType<string>();
    /// <summary>
    /// 针对 IIdentity<TKey> 实例获取对应的 IdentityType
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TKey>(this IIdentity<TKey> identity) where TKey : IConvertible, IEquatable<TKey> => identity.GetIdentityType<IIdentity<TKey>, TKey>();
    /// <summary>
    /// 针对任意引用类型获取对应的身份 IdentityType
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TIdentity, TKey>(this TIdentity identity) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey> => identity switch
    {
        User<TKey> => IdentityType.User,
        Role<TKey> => IdentityType.Role,
        Group<TKey> => IdentityType.Group,
        Level<TKey> => IdentityType.Level,
        _ => IdentityType.System
    };

    /// <summary>
    /// 获取身份相关调用错误提示
    /// </summary>
    /// <param name="errors"></param>
    /// <returns></returns>
    public static string GetErrorMessage(this IdentityResult identityResult) => (!identityResult.Succeeded && identityResult.Errors.Any()) ? identityResult.Errors.Select(x => x.Description).ToString<string>() : identityResult.ToString();

    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddIdentityDataService(this IServiceCollection services)
    {
        services.AddScoped(typeof(IDataService<>), typeof(DataService<>));
        services.AddScoped(typeof(IAttachmentService), typeof(AttachmentService));
        services.AddScoped(typeof(IDataWithAttachmentService<>), typeof(DataWithAttachmentService<>));
        services.AddScoped(typeof(IDataWithAdditionalService<,>), typeof(DataWithAdditionalService<,>));
        services.AddScoped(typeof(IDataWithCollectionService<,>), typeof(DataWithCollectionService<,>));
        services.AddScoped(typeof(IDataWithAttachmentAndAdditionalService<,>), typeof(DataWithAttachmentAndAdditionalService<,>));
        return services;
    }
}